/*
*********************************************************************************************************
*
*	模块名称 : OLED模块
*	文件名称 : bsp_oled_spi.c
*	版    本 : V1.0
*	说    明 : OLED 屏(128x64)底层驱动程序，兼容SSD1315/SSD1306 使用4线SPI控制
*	修改记录 :
*
*	Copyright (C), 2018-2020
*
*********************************************************************************************************
*/

/*includes----------------------------------------------*/
#include "bsp_oled_spi.h"
#include "stm32g0xx_hal.h"
#include "oledfont.h"
#include "bsp_timer.h"

#ifdef OLED_USE_HARD_SPI
#include "bsp_hal_spi.h"
#endif

/*definition--------------------------------------------*/
#define  Oled_DelayNus(a)   bsp_DelayUS(a)
#define  Oled_DelayNms(b)   bsp_DelayMS(b)


//----------------------------------------------------------------------------//
#define OLED_SCK_D0_GPIO_RCC()      __HAL_RCC_GPIOA_CLK_ENABLE()
#define OLED_SCK_D0_GPIO_PIN	    GPIO_PIN_1			/* SCK/Do */
#define OLED_SCK_D0_GPIO_PORT       GPIOA

#define OLED_MOSI_D1_GPIO_RCC()     __HAL_RCC_GPIOA_CLK_ENABLE()
#define OLED_MOSI_D1_GPIO_PIN	    GPIO_PIN_2			/* MOSI/D1 */
#define OLED_MOSI_D1_GPIO_PORT      GPIOA

#define OLED_RES_GPIO_RCC()         __HAL_RCC_GPIOA_CLK_ENABLE()
#define OLED_RES_GPIO_PIN	        GPIO_PIN_5			/* RES*/
#define OLED_RES_GPIO_PORT          GPIOA

#define OLED_DC_GPIO_RCC()          __HAL_RCC_GPIOA_CLK_ENABLE()
#define OLED_DC_GPIO_PIN	        GPIO_PIN_3			/* DC */
#define OLED_DC_GPIO_PORT           GPIOA

#define OLED_CS_GPIO_RCC()          __HAL_RCC_GPIOA_CLK_ENABLE()
#define OLED_CS_GPIO_PIN	        GPIO_PIN_4			/* CS */
#define OLED_CS_GPIO_PORT           GPIOA
//----------------------------------------------------------------------------//
#ifndef OLED_USE_HARD_SPI           //没有使用硬件SPI时模拟spi
#define OLED_SCK_D0_HIGH()          HAL_GPIO_WritePin(OLED_SCK_D0_GPIO_PORT,OLED_SCK_D0_GPIO_PIN,GPIO_PIN_SET)			/* （DO）输出高 */
#define OLED_SCK_D0_LOW()           HAL_GPIO_WritePin(OLED_SCK_D0_GPIO_PORT,OLED_SCK_D0_GPIO_PIN,GPIO_PIN_RESET)		/* （DO）输出低 */

#define OLED_MOSI_D1_HIGH()         HAL_GPIO_WritePin(OLED_MOSI_D1_GPIO_PORT,OLED_MOSI_D1_GPIO_PIN,GPIO_PIN_SET)		/* （D1）输出高 */
#define OLED_MOSI_D1_LOW()          HAL_GPIO_WritePin(OLED_MOSI_D1_GPIO_PORT,OLED_MOSI_D1_GPIO_PIN,GPIO_PIN_RESET)		/* （D1）输出低 */
#endif

#define OLED_RES_HIGH()             HAL_GPIO_WritePin(OLED_RES_GPIO_PORT,OLED_RES_GPIO_PIN,GPIO_PIN_SET)			    /* （RES）输出高 */
#define OLED_RES_LOW()              HAL_GPIO_WritePin(OLED_RES_GPIO_PORT,OLED_RES_GPIO_PIN,GPIO_PIN_RESET)			    /* （RES）输出低 */

#define OLED_DC_HIGH()              HAL_GPIO_WritePin(OLED_DC_GPIO_PORT,OLED_DC_GPIO_PIN,GPIO_PIN_SET)			        /* （DC）输出高 */
#define OLED_DC_LOW()               HAL_GPIO_WritePin(OLED_DC_GPIO_PORT,OLED_DC_GPIO_PIN,GPIO_PIN_RESET)		        /* （DC）输出低 */

#define OLED_CS_HIGH()              HAL_GPIO_WritePin(OLED_CS_GPIO_PORT,OLED_CS_GPIO_PIN,GPIO_PIN_SET)			        /* （CS）输出高 */
#define OLED_CS_LOW()               HAL_GPIO_WritePin(OLED_CS_GPIO_PORT,OLED_CS_GPIO_PIN,GPIO_PIN_RESET)		        /* （CS）输出低 */

#define GRAM_MAX_COLUMN 144
static uint8_t OLED_GRAM[GRAM_MAX_COLUMN][OLED_MAX_ROW / 8] = {0};

//-------------------------------------------------------------------------//
/*
@brief			初始化OLED与单片机的IO接口
@param			无
@retval			无
*/
static void OLED_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    OLED_SCK_D0_GPIO_RCC();
    OLED_MOSI_D1_GPIO_RCC();
    OLED_RES_GPIO_RCC();
    OLED_DC_GPIO_RCC();
    OLED_CS_GPIO_RCC();

    /*Configure GPIO pins */
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pin   = OLED_SCK_D0_GPIO_PIN;
    HAL_GPIO_Init(OLED_SCK_D0_GPIO_PORT, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = OLED_MOSI_D1_GPIO_PIN;
    HAL_GPIO_Init(OLED_MOSI_D1_GPIO_PORT, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = OLED_RES_GPIO_PIN;
    HAL_GPIO_Init(OLED_RES_GPIO_PORT, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = OLED_DC_GPIO_PIN;
    HAL_GPIO_Init(OLED_DC_GPIO_PORT, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = OLED_CS_GPIO_PIN;
    HAL_GPIO_Init(OLED_CS_GPIO_PORT, &GPIO_InitStruct);

    OLED_SCK_D0_HIGH();
    OLED_MOSI_D1_HIGH();
    OLED_RES_HIGH();
    OLED_DC_HIGH();
    OLED_CS_HIGH();
}


#ifndef OLED_USE_HARD_SPI           //没有使用硬件SPI时模拟spi
/*
@brief			模拟SPI发送一个字节
@param			data：发送的数据
@retval			无
*/
static void SPI_Write_Byte(unsigned char data)
{
    unsigned char i;	//定义变量
    for(i = 0; i < 8; i++)	//循环8次
    {
        OLED_SCK_D0_LOW();	//将时钟线拉低
        Oled_DelayNus(1);	//延迟

        if(data & 0x80)	//数据从高位-->低位依次发送
            OLED_MOSI_D1_HIGH();	//数据为为1
        else
            OLED_MOSI_D1_LOW();	//数据位为0

        data <<= 1;	//数据左移1位

        Oled_DelayNus(1);	//延迟
        OLED_SCK_D0_HIGH();	//时钟线拉高，把数据发送出去
        Oled_DelayNus(1);	//延迟
    }

}
#endif


/*
@brief			对OLED写入一个字节
@param			dat：数据
cmd：1，写诶数据；0，写入命令
@retval			无
*/
void bsp_OledWriteByte(unsigned char dat, unsigned char cmd)
{
    if(cmd)  //如果cmd为高，则发送的是数据
        OLED_DC_HIGH();	//将DC拉高
    else    //如果cmd为低，则发送的是命令
        OLED_DC_LOW();	//将DC拉低

    OLED_CS_LOW(); //片选拉低，选通器件
    Oled_DelayNus(1);	//延迟
    /* 通过SPI写入数据 */
#ifdef OLED_USE_HARD_SPI

#else
    SPI_Write_Byte(dat); //发送数据
#endif

    OLED_CS_HIGH(); //片选拉高，关闭器件
    OLED_DC_HIGH(); //DC拉高，空闲时为高电平
}

//反显函数
void bsp_OledColorTurn(uint8_t i)
{
    if(i == 0)
    {
        bsp_OledWriteByte(0xA6, OLED_CMD); //正常显示
    }
    if(i == 1)
    {
        bsp_OledWriteByte(0xA7, OLED_CMD); //反色显示
    }
}

//屏幕旋转180度
void bsp_OledDisplayTurn(uint8_t i)
{
    if(i == 0)
    {
        bsp_OledWriteByte(0xC8, OLED_CMD); //正常显示
        bsp_OledWriteByte(0xA1, OLED_CMD);
    }
    if(i == 1)
    {
        bsp_OledWriteByte(0xC0, OLED_CMD); //反转显示
        bsp_OledWriteByte(0xA0, OLED_CMD);
    }
}



//开启OLED显示
void bsp_OledDisplayOn(void)
{
    bsp_OledWriteByte(0x8D, OLED_CMD); //电荷泵使能
    bsp_OledWriteByte(0x14, OLED_CMD); //开启电荷泵
    bsp_OledWriteByte(0xAF, OLED_CMD); //点亮屏幕
}

//关闭OLED显示
void bsp_OledDisplayOff(void)
{
    bsp_OledWriteByte(0x8D, OLED_CMD); //电荷泵使能
    bsp_OledWriteByte(0x10, OLED_CMD); //关闭电荷泵
    bsp_OledWriteByte(0xAE, OLED_CMD); //关闭屏幕
}

//更新显存到OLED
void bsp_OledRefresh(void)
{
    uint8_t i, n;
    for(i = 0; i < 8; i++)
    {
        bsp_OledWriteByte(0xb0 + i, OLED_CMD); //设置行起始地址
        bsp_OledWriteByte(0x00, OLED_CMD);  //设置低列起始地址
        bsp_OledWriteByte(0x10, OLED_CMD);  //设置高列起始地址
        for(n = 0; n < OLED_MAX_COLUMN; n++)
            bsp_OledWriteByte(OLED_GRAM[n][i], OLED_DATA);
    }
}
//清屏函数
void bsp_OledClear(void)
{
    uint8_t i, n;
    for(i = 0; i < 8; i++)
    {
        for(n = 0; n < OLED_MAX_COLUMN; n++)
        {
            OLED_GRAM[n][i] = 0; //清除所有数据
        }
    }
    bsp_OledRefresh();//更新显示
}
void bsp_OledClearNoRefresh(void)
{
    uint8_t i, n;
    for(i = 0; i < 8; i++)
    {
        for(n = 0; n < OLED_MAX_COLUMN; n++)
        {
            OLED_GRAM[n][i] = 0; //清除所有数据
        }
    }
}

//画点
//x:0~127
//y:0~63
//t:1 填充 0,清空
void bsp_OledDrawPoint(uint8_t x, uint8_t y, uint8_t t)
{
    uint8_t i, m, n;
    i = y / 8;
    m = y % 8;
    n = 1 << m;
    if(t)
    {
        OLED_GRAM[x][i] |= n;
    }
    else
    {
        OLED_GRAM[x][i] = ~OLED_GRAM[x][i];
        OLED_GRAM[x][i] |= n;
        OLED_GRAM[x][i] = ~OLED_GRAM[x][i];
    }
}

//画线
//x1,y1:起点坐标
//x2,y2:结束坐标
void bsp_OledDrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode)
{
    uint16_t t;
    int xerr = 0, yerr = 0, delta_x, delta_y, distance;
    int incx, incy, uRow, uCol;
    delta_x = x2 - x1; //计算坐标增量
    delta_y = y2 - y1;
    uRow = x1; //画线起点坐标
    uCol = y1;
    if(delta_x > 0)incx = 1; //设置单步方向
    else if(delta_x == 0)incx = 0; //垂直线
    else
    {
        incx = -1;
        delta_x = -delta_x;
    }
    if(delta_y > 0)incy = 1;
    else if(delta_y == 0)incy = 0; //水平线
    else
    {
        incy = -1;
        delta_y = -delta_x;
    }
    if(delta_x > delta_y)distance = delta_x; //选取基本增量坐标轴
    else distance = delta_y;
    for(t = 0; t < distance + 1; t++)
    {
        bsp_OledDrawPoint(uRow, uCol, mode); //画点
        xerr += delta_x;
        yerr += delta_y;
        if(xerr > distance)
        {
            xerr -= distance;
            uRow += incx;
        }
        if(yerr > distance)
        {
            yerr -= distance;
            uCol += incy;
        }
    }
}
//x,y:圆心坐标
//r:圆的半径
void bsp_OledDrawCircle(uint8_t x, uint8_t y, uint8_t r)
{
    int a, b, num;
    a = 0;
    b = r;
    while(2 * b * b >= r * r)
    {
        bsp_OledDrawPoint(x + a, y - b, 1);
        bsp_OledDrawPoint(x - a, y - b, 1);
        bsp_OledDrawPoint(x - a, y + b, 1);
        bsp_OledDrawPoint(x + a, y + b, 1);

        bsp_OledDrawPoint(x + b, y + a, 1);
        bsp_OledDrawPoint(x + b, y - a, 1);
        bsp_OledDrawPoint(x - b, y - a, 1);
        bsp_OledDrawPoint(x - b, y + a, 1);

        a++;
        num = (a * a + b * b) - r * r; //计算画的点离圆心的距离
        if(num > 0)
        {
            b--;
            a--;
        }
    }
}



//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size1:选择字体 6x8/6x12/8x16/12x24
//mode:0,反色显示;1,正常显示
void bsp_OledShowChar(uint8_t x, uint8_t y, char chr, uint8_t size1, uint8_t mode)
{
    uint8_t i, m, temp, size2, chr1;
    uint8_t x0 = x, y0 = y;
    if(size1 == 8)size2 = 6;
    else size2 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * (size1 / 2); //得到字体一个字符对应点阵集所占的字节数
    chr1 = chr - ' '; //计算偏移后的值
    for(i = 0; i < size2; i++)
    {
        if(size1 == 8)
        {
            temp = asc2_0806[chr1][i];   //调用0806字体
        }
        else if(size1 == 12)
        {
            temp = asc2_1206[chr1][i];   //调用1206字体
        }
        else if(size1 == 16)
        {
            temp = asc2_1608[chr1][i];   //调用1608字体
        }
//        else if(size1 == 24)
//        {
//            temp = asc2_2412[chr1][i];   //调用2412字体
//        }
        else return;
        for(m = 0; m < 8; m++)
        {
            if(temp & 0x01)bsp_OledDrawPoint(x, y, mode);
            else bsp_OledDrawPoint(x, y, !mode);
            temp >>= 1;
            y++;
        }
        x++;
        if((size1 != 8) && ((x - x0) == size1 / 2))
        {
            x = x0;
            y0 = y0 + 8;
        }
        y = y0;
    }
}


//显示字符串
//x,y:起点坐标
//size1:字体大小
//*chr:字符串起始地址
//mode:0,反色显示;1,正常显示
void bsp_OledShowString(uint8_t x, uint8_t y, char* chr, uint8_t size1, uint8_t mode)
{
    while((*chr >= ' ') && (*chr <= '~')) //判断是不是非法字符!
    {
        bsp_OledShowChar(x, y, *chr, size1, mode);
        if(size1 == 8)x += 6;
        else x += size1 / 2;
        chr++;
    }
}

//m^n
uint32_t OLED_Pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;
    while(n--)
    {
        result *= m;
    }
    return result;
}

//显示数字
//x,y :起点坐标
//num :要显示的数字
//len :数字的位数
//size:字体大小
//mode:0,反色显示;1,正常显示
void bsp_OledShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1, uint8_t mode)
{
    uint8_t t, temp, m = 0;
    if(size1 == 8)m = 2;
    for(t = 0; t < len; t++)
    {
        temp = (num / OLED_Pow(10, len - t - 1)) % 10;
        if(temp == 0)
        {
            bsp_OledShowChar(x + (size1 / 2 + m)*t, y, '0', size1, mode);
        }
        else
        {
            bsp_OledShowChar(x + (size1 / 2 + m)*t, y, temp + '0', size1, mode);
        }
    }
}

//显示汉字
//x,y:起点坐标
//num:汉字对应的序号
//mode:0,反色显示;1,正常显示
void bsp_OledShowChinese(uint8_t x, uint8_t y, uint8_t num, uint8_t size1, uint8_t mode)
{
    uint8_t m, temp;
    uint8_t x0 = x, y0 = y;
    uint16_t i, size3 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * size1; //得到字体一个字符对应点阵集所占的字节数
    for(i = 0; i < size3; i++)
    {
        if(size1 == 16)
        {
            temp = Hzk1[num][i];   //调用16*16字体
        }
//		else if(size1==24)
//				{temp=Hzk2[num][i];}//调用24*24字体
//		else if(size1==32)
//				{temp=Hzk3[num][i];}//调用32*32字体
//		else if(size1==64)
//				{temp=Hzk4[num][i];}//调用64*64字体
//		else return;
        for(m = 0; m < 8; m++)
        {
            if(temp & 0x01)bsp_OledDrawPoint(x, y, mode);
            else bsp_OledDrawPoint(x, y, !mode);
            temp >>= 1;
            y++;
        }
        x++;
        if((x - x0) == size1)
        {
            x = x0;
            y0 = y0 + 8;
        }
        y = y0;
    }
}

//num 显示汉字的个数
//space 每一遍显示的间隔
//mode:0,反色显示;1,正常显示
void bsp_OledScrollDisplay(uint8_t num, uint8_t space, uint8_t mode)
{
    uint8_t i, n, t = 0, m = 0, r;
    while(1)
    {
        if(m == 0)
        {
            bsp_OledShowChinese(128, 24, t, 16, mode); //写入一个汉字保存在OLED_GRAM[][]数组中
            t++;
        }
        if(t == num)
        {
            for(r = 0; r < 16 * space; r++) //显示间隔
            {
                for(i = 1; i < GRAM_MAX_COLUMN; i++)
                {
                    for(n = 0; n < 8; n++)
                    {
                        OLED_GRAM[i - 1][n] = OLED_GRAM[i][n];
                    }
                }
                bsp_OledRefresh();
            }
            t = 0;
        }
        m++;
        if(m == 16)
        {
            m = 0;
        }
        for(i = 1; i < 144; i++) //实现左移
        {
            for(n = 0; n < 8; n++)
            {
                OLED_GRAM[i - 1][n] = OLED_GRAM[i][n];
            }
        }
        bsp_OledRefresh();
    }
}

//x,y：起点坐标
//sizex,sizey,图片长宽
//BMP[]：要写入的图片数组
//mode:0,反色显示;1,正常显示
void bsp_OledShowPicture(uint8_t x, uint8_t y, uint8_t sizex, uint8_t sizey, uint8_t BMP[], uint8_t mode)
{
    uint16_t j = 0;
    uint8_t i, n, temp, m;
    uint8_t x0 = x, y0 = y;
    sizey = sizey / 8 + ((sizey % 8) ? 1 : 0);
    for(n = 0; n < sizey; n++)
    {
        for(i = 0; i < sizex; i++)
        {
            temp = BMP[j];
            j++;
            for(m = 0; m < 8; m++)
            {
                if(temp & 0x01)bsp_OledDrawPoint(x, y, mode);
                else bsp_OledDrawPoint(x, y, !mode);
                temp >>= 1;
                y++;
            }
            x++;
            if((x - x0) == sizex)
            {
                x = x0;
                y0 = y0 + 8;
            }
            y = y0;
        }
    }
}


/*
@brief			OLED初始化函数
@param			无
@retval			无
*/
void bsp_OledInit(void)
{
    OLED_GPIO_Init();	//GPIO口初始化

    OLED_RES_HIGH();
    Oled_DelayNms(100);
    OLED_RES_LOW();
    Oled_DelayNms(400);	//延迟，由于单片机上电初始化比OLED快，所以必须加上延迟，等待OLED上电初始化完成
    OLED_RES_HIGH();
    Oled_DelayNms(200);

    bsp_OledWriteByte(0xAE, OLED_CMD); //--turn off oled panel
    bsp_OledWriteByte(0x00, OLED_CMD); //---set low column address
    bsp_OledWriteByte(0x10, OLED_CMD); //---set high column address
    bsp_OledWriteByte(0x40, OLED_CMD); //--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    bsp_OledWriteByte(0x81, OLED_CMD); //--set contrast control register
    bsp_OledWriteByte(0xCF, OLED_CMD); // Set SEG Output Current Brightness
    bsp_OledWriteByte(0xA1, OLED_CMD); //--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    bsp_OledWriteByte(0xC8, OLED_CMD); //Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    bsp_OledWriteByte(0xA6, OLED_CMD); //--set normal display
    bsp_OledWriteByte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64)
    bsp_OledWriteByte(0x3f, OLED_CMD); //--1/64 duty
    bsp_OledWriteByte(0xD3, OLED_CMD); //-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
    bsp_OledWriteByte(0x00, OLED_CMD); //-not offset
    bsp_OledWriteByte(0xd5, OLED_CMD); //--set display clock divide ratio/oscillator frequency
    bsp_OledWriteByte(0x80, OLED_CMD); //--set divide ratio, Set Clock as 100 Frames/Sec
    bsp_OledWriteByte(0xD9, OLED_CMD); //--set pre-charge period
    bsp_OledWriteByte(0xF1, OLED_CMD); //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    bsp_OledWriteByte(0xDA, OLED_CMD); //--set com pins hardware configuration
    bsp_OledWriteByte(0x12, OLED_CMD);
    bsp_OledWriteByte(0xDB, OLED_CMD); //--set vcomh
    bsp_OledWriteByte(0x40, OLED_CMD); //Set VCOM Deselect Level
    bsp_OledWriteByte(0x20, OLED_CMD); //-Set Page Addressing Mode (0x00/0x01/0x02)
    bsp_OledWriteByte(0x02, OLED_CMD); //
    bsp_OledWriteByte(0x8D, OLED_CMD); //--set Charge Pump enable/disable
    bsp_OledWriteByte(0x14, OLED_CMD); //--set(0x10) disable
    bsp_OledWriteByte(0xA4, OLED_CMD); // Disable Entire Display On (0xa4/0xa5)
    bsp_OledWriteByte(0xA6, OLED_CMD); // Disable Inverse Display On (0xa6/a7)
    bsp_OledClear();
    bsp_OledWriteByte(0xAF, OLED_CMD);
}


